﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using EmbeddedSharp.Core;
using Zove.Runtime.Core;

namespace Ascend.Runtime.Core {

    /// <summary>
    /// http://ascendwiki.cheme.cmu.edu/IMPORT
    /// 
    /// The process used is like this:
    /// 1. ASCEND finds a statement like IMPORT "mything"
    /// 2. It looks at the first Import Handler on its list
    /// 3. It requests the Import Handler to translate the string mything into a filename corresponding to that particular handler
    /// 4. ASCEND looks to see if the named file exists anywhere in the ASCENDLIBRARY path
    /// 5. It moves through the list of Import Handlers until a file is found in the search path that matches the filename returned by the Import Handler
    /// 6. ASCEND then calls the 'import' function for that particular Import Handler, passing it the full pathname of the file to be imported.
    /// 7. The 'import' function can do whatever it likes. It could load a new solver, add external methods or blackbox functions, launch another program, etc. Any functionality exposed via the libascend.so API is accessible. 
    /// </summary>
    public class AscendClassFinder : IClassFinder {
        public AscendClassFinder() {
            Index = new Dictionary<string, AscendEntity>();
            Workspaces = new List<string>();
        }

        public AscendClassFinder(params string[] workspaces)
            : this() {
            Workspaces.AddRange(workspaces);

            foreach (string workspace in workspaces) {
                Scan(workspace);
            }
        }

        public Dictionary<string, AscendEntity> Index { get; set; }

        public List<string> Workspaces { get; set; }

        #region IClassFinder Members

        /// <summary>
        /// Add a required file
        /// </summary>
        /// <param name="fileName"></param>
        public void AddClassPath(string fileName) {
            FileInfo file = new FileInfo(fileName);
            Scan(file);
        }

        public string[] ClassPaths {
            get {
                return Workspaces.ToArray();
            }
        }

        public string GetLocation(string className) {
            AscendEntity file = Index[className];
            if (file != null) {
                return file.Source.FileName;
            }

            throw new FileNotFoundException(className);
        }

        /// <summary>
        /// Returns the source from the class
        /// </summary>
        /// <param name="className">class name</param>
        /// <returns>the source of the class</returns>
        public string GetSource(string className) {
            AscendEntity file = Index[className];
            if (file != null) {
                FileStream stream = File.OpenRead(file.Source.FileName);
                StreamReader reader = new StreamReader(stream);
                char[] buffer = new char[file.Source.End - file.Source.Start];
                reader.Read(buffer, file.Source.Start, file.Source.End);
                string source = new string(buffer);

                return source;
            }

            throw new FileNotFoundException(className);
        }

        #endregion

        public void Scan(string directory) {
            var di = new DirectoryInfo(directory);
            foreach (FileInfo file in di.GetFiles("*.a4c", SearchOption.AllDirectories)) {
                Scan(file);
            }

            foreach (FileInfo file in di.GetFiles("*.a4l", SearchOption.AllDirectories)) {
                Scan(file);
            }
        }

        public void Scan(FileInfo file) {
            string source = File.ReadAllText(file.FullName);
            var lexer = new AscendLexer(source);

            Token token = null;
            while (lexer.HasMoreTokens()) {
                var entity = new AscendEntity();

                string keyword = string.Empty;
                while (lexer.HasMoreTokens()) {
                    keyword = (token = lexer.NextToken()).Value;
                    switch (keyword) {
                        case "MODEL":
                            entity.Type = AscendEntityType.Model;
                            entity = ReadEntity(file.FullName, "MODEL", lexer);

                            if (!Index.ContainsKey(entity.Name)) {
                                Index.Add(entity.Name, entity);
                            } else {
                                Console.WriteLine("Duplicated entry: " + entity.Name);
                            }
                            break;
                        case "ATOM":
                            entity.Type = AscendEntityType.Atom;
                            entity = ReadEntity(file.FullName, "ATOM", lexer);

                            if (!Index.ContainsKey(entity.Name)) {
                                Index.Add(entity.Name, entity);
                            } else {
                                Console.WriteLine("Duplicated entry: " + entity.Name);
                            }
                            break;
                        case "CONSTANT":
                            entity.Type = AscendEntityType.Constant;
                            entity.Name = lexer.PeekToken().Value;
                            entity.Source.FileName = file.FullName;
                            break;
                        case "UNIVERSAL":
                            break;
                        default:
                            break;
                    }
                }
            }
        }

        private AscendEntity ReadEntity(
            string fileName, string keyword, AscendLexer lexer) {
            var entity = new AscendEntity();
            entity.Name = lexer.NextToken().Value;

            //var body = new StringBuilder(keyword + " " + entity.Name);
            Token token = null;

            int start = lexer.Index;
            while (lexer.HasMoreTokens()) {
                token = lexer.NextToken();
                //body.Append(token.Value);

                if (token.Value == "END") {
                    token = lexer.NextToken();
                    //body.Append(token.Value);

                    if (token.Value == entity.Name) {
                        break;
                    }
                }
            }
            //body.Append(token.Value).Append(";");
            int end = lexer.Index;

            entity.Source = new SourceSpan { FileName = fileName, Start = start, End = end };

            return entity;
        }

        /// <summary>
        /// Get the next token
        /// </summary>
        /// <param name="source"></param>
        /// <param name="start"></param>
        /// <returns></returns>
        public static string NextToken(string source, int start) {
            int count = 0;
            start = SkipComments(source, start);
            for (;
                char.IsLetterOrDigit(source[start + count]) ||
                source[start + count] == '_';
                count++) ;

            return source.Substring(start, count);
        }

        /// <summary>
        /// Skips a section of comment begins with (* and ends with *).
        /// </summary>
        public static int SkipComments(string source, int start) {
            char chr = source[start];
            var buffer = new StringBuilder();

            if (chr == '(') {
                char cxx = source[start + 1];
                if (cxx == '*') {
                    start++;
                    buffer.Append(chr).Append(cxx);
                    while (start + 1 < source.Length) {
                        cxx = source[++start];
                        buffer.Append(cxx);
                        if (cxx == '*' && source[start + 1] == ')') {
                            cxx = source[++start];
                            buffer.Append(cxx);
                            break;
                        }
                    }
                }
            }

            return start; // buffer.ToString();
        }
    }
}